libobs_simple\sources\windows\sources/
monitor_capture.rs1use std::sync::Arc;
5
6use super::ObsDisplayCaptureMethod;
7use crate::error::ObsSimpleError;
8use crate::{define_object_manager, sources::macro_helper::impl_custom_source};
9use display_info::DisplayInfo;
12use libobs_simple_macro::obs_object_impl;
13use libobs_wrapper::run_with_obs;
14use libobs_wrapper::runtime::ObsRuntime;
15use libobs_wrapper::scenes::{ObsSceneItemRef, SceneItemExtSceneTrait};
16use libobs_wrapper::{
17 data::{ObsObjectBuilder, ObsObjectUpdater},
18 scenes::ObsSceneRef,
19 sources::{ObsSourceBuilder, ObsSourceRef, ObsSourceTrait},
20 unsafe_send::Sendable,
21 utils::ObsError,
22};
23use num_traits::ToPrimitive;
24use windows::Win32::UI::HiDpi::{
25 GetAwarenessFromDpiAwarenessContext, GetThreadDpiAwarenessContext,
26 SetProcessDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
27 DPI_AWARENESS_UNAWARE,
28};
29
30define_object_manager!(
32 #[derive(Debug)]
34 struct MonitorCaptureSource("monitor_capture", *mut libobs::obs_source) for ObsSourceRef {
35 #[obs_property(type_t = "string", settings_key = "monitor_id")]
36 monitor_id_raw: String,
37
38 #[obs_property(type_t = "bool")]
39 capture_cursor: bool,
41
42 #[obs_property(type_t = "bool")]
43 compatibility: bool,
45
46 #[obs_property(type_t = "bool")]
47 force_sdr: bool,
49
50 capture_method: Option<ObsDisplayCaptureMethod>,
51 }
52);
53
54#[obs_object_impl]
55impl MonitorCaptureSource {
56 pub fn get_monitors() -> Result<Vec<Sendable<DisplayInfo>>, ObsSimpleError> {
58 Ok(DisplayInfo::all()
59 .map_err(ObsSimpleError::DisplayInfoError)?
60 .into_iter()
61 .map(Sendable)
62 .collect())
63 }
64
65 pub fn set_monitor(self, monitor: &Sendable<DisplayInfo>) -> Self {
66 self.set_monitor_id_raw(monitor.0.name.as_str())
67 }
68}
69
70fn is_thread_dpi_unaware(runtime: &ObsRuntime) -> Result<bool, ObsError> {
71 run_with_obs!(runtime, (), move || {
72 unsafe {
73 let ctx = GetThreadDpiAwarenessContext();
75 GetAwarenessFromDpiAwarenessContext(ctx) == DPI_AWARENESS_UNAWARE
76 }
77 })
78}
79
80fn set_dpi_awareness_if_needed(runtime: &ObsRuntime) -> Result<(), ObsError> {
81 if is_thread_dpi_unaware(runtime)? {
82 log::warn!("The current thread is DPI unaware. Setting the DPI awareness context to Per Monitor Aware V2 to allow DXGI capture method to work correctly.");
83 } else {
84 return Ok(());
85 }
86
87 let set_result = unsafe {
88 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
90 };
91
92 if let Err(e) = set_result {
93 log::warn!("Could not set DPI awareness context: {:?}. This is fine if you don't want to use DXGI capture or if you have already specified DPI awareness in the application manifest.", e);
94 Err(ObsError::InvalidOperation("Process is not DPI aware and could not set DPI awareness. DPI awareness is required for DXGI monitor capture however".into()))
95 } else {
96 Ok(())
97 }
98}
99
100impl<'a> MonitorCaptureSourceUpdater<'a> {
101 pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Result<Self, ObsError> {
102 if method == ObsDisplayCaptureMethod::MethodDXGI {
103 set_dpi_awareness_if_needed(self.runtime())?;
104 }
105 self.get_settings_updater()
106 .set_int_ref("method", method.to_i32().unwrap() as i64);
107
108 Ok(self)
109 }
110}
111
112impl MonitorCaptureSourceBuilder {
113 pub fn set_capture_method(mut self, method: ObsDisplayCaptureMethod) -> Self {
116 self.capture_method = Some(method);
117
118 self
119 }
120}
121
122pub type GeneralSourceRef = Arc<Box<dyn ObsSourceTrait>>;
123impl ObsSourceBuilder for MonitorCaptureSourceBuilder {
124 type T = MonitorCaptureSource;
125
126 fn build(self) -> Result<Self::T, ObsError>
127 where
128 Self: Sized,
129 {
130 if self.capture_method == Some(ObsDisplayCaptureMethod::MethodDXGI) {
131 set_dpi_awareness_if_needed(self.runtime())?;
132 }
133
134 let runtime = self.runtime.clone();
135 let obj_info = self.object_build()?;
136
137 let res = ObsSourceRef::new_from_info(obj_info, runtime)?;
138 MonitorCaptureSource::new(res)
139 }
140
141 fn add_to_scene(mut self, scene: &mut ObsSceneRef) -> Result<ObsSceneItemRef<Self::T>, ObsError>
142 where
143 Self: Sized,
144 {
145 self.get_settings_updater().set_int_ref(
147 "method",
148 ObsDisplayCaptureMethod::MethodWgc.to_i32().unwrap() as i64,
149 );
150
151 let method_to_set = self.capture_method;
152
153 let mut res = self.build()?;
154 let scene_item = scene.add_source(res.clone())?;
155
156 if let Some(method) = method_to_set {
157 res.create_updater()?
158 .set_capture_method(method)? .update()?;
160 }
161
162 Ok(scene_item)
163 }
164}
165
166impl_custom_source!(MonitorCaptureSource);